"use strict";
var DeletedVideosMemory;

DeletedVideosMemory = (function () {
    /**
     * The instance of the DeletedVideosMemory class.
     *
     * @type DeletedVideosMemory
     */
    var instance,

    /**
     * The extension's configuration object.
     *
     * @type Settings
     */
    settings,

    /**
     * A map of the video ID to the Date object specifying the moment the video
     * was removed the the "new videos" list.
     *
     * @type Object
     */
    deletedVideos;

    /**
     * Returns the instance of the DeletedVideosMemory class or creates one if
     * neccessary.
     *
     * @constructor
     * @returns {DeletedVideosMemory} The application-wide instance of the
     *          DeletedVideosMemory class.
     */
    function DeletedVideosMemory() {
        if (instance) {
            return instance;
        }
        if (this === window) {
            throw new Error("This function must be used as a constructor");
        }
        instance = this;
        settings = new Settings();
        loadDeletedVideos();
        importFilter();
        removeExpiredVideoEntries();
        setInterval(function () {
            removeExpiredVideoEntries();
        }, 86400000); // clean the cache every 24 hours
    }

    /**
     * Reloads the in-memory storage of the deleted videos from the persistent
     * HTML5 local storage. The method also removes the expired video entries.
     */
    DeletedVideosMemory.prototype.reload = function () {
        loadDeletedVideos();
        removeExpiredVideoEntries();
    };

    /**
     * Returns <code>true</code> if the video identified by the provided video
     * ID has been deleted from the "new videos" list.
     *
     * @param {String} videoId ID of the video to test.
     * @returns {Boolean} <code>true</code> if the video was deleted.
     */
    DeletedVideosMemory.prototype.isDeleted = function (videoId) {
        return deletedVideos.hasOwnProperty(videoId) &&
                !!deletedVideos[videoId];
    };

    /**
     * Filters the provided "new videos" feed and removes all videos that has
     * been deleted from the feed by the user.
     *
     * @param {Array|Object} feed The "new videos" feed to filter.
     * @returns {Array} Filtered feed.
     */
    DeletedVideosMemory.prototype.filterFeed = function (feed) {
        var result, i;
        if (!(feed instanceof Array)) {
            feed = linearizeFeed(feed);
        }
        result = [];
        for (i = feed.length; i--;) {
            if (!this.isDeleted(feed[i].id)) {
                result.unshift(feed[i]);
            }
        }
        return result;
    };

    /**
     * An event handler to execute whenever the user removes a video from the
     * "new videos" feed.
     *
     * @param {String} videoId ID of the video deleted from the "new videos"
     *        feed.
     */
    DeletedVideosMemory.prototype.onVideoDeleted = function (videoId) {
        if (typeof videoId !== "string") {
            throw new TypeError("The video ID must be a string");
        }
        deletedVideos[videoId] = new Date();
        saveDeletedVideos();
    };

    /**
     * Linearizes an object-based representation of the provided new videos
     * list (feed) into an array-based representation by concatenating all
     * sub-arrays of the provided feed into a single array.
     *
     * @param {Object} feed Object-based representation of the new videos. Keys
     *        are channel names, values are arrays of video entries.
     * @returns {Array} Linearized representation of the provided feed.
     */
    function linearizeFeed(feed) {
        var i, linear;
        linear = [];
        for (i in feed) {
            if (feed.hasOwnProperty(i)) {
                linear = linear.concat(feed[i]);
            }
        }
        return linear;
    }

    /**
     * Removes the entries from the in-memory storage if videos deleted from
     * the "new videos" list that have expired. The method saves the in-memory
     * storage to the persistent HTML5 storage if any entry is deleted.
     *
     * @see Settings.getDeletedVideosExpiration()
     */
    function removeExpiredVideoEntries() {
        var expirationTimestamp, expiration, updated, videoId;
        if (!settings.getDeletedVideosExpiration()) {
            return;
        }
        expiration = settings.getDeletedVideosExpiration() * 1000;
        expirationTimestamp = (new Date()).getTime() - expiration;
        updated = false;
        for (videoId in deletedVideos) {
            if (!deletedVideos.hasOwnProperty(videoId)) {
                continue;
            }
            if (!deletedVideos[videoId]) {
                continue;
            }
            if (deletedVideos[videoId].getTime() < expirationTimestamp) {
                deletedVideos[videoId] = undefined;
                delete deletedVideos[videoId];
                updated = true;
            }
        }
        if (updated) {
            saveDeletedVideos();
        }
    }

    /**
     * Loads and de-serializes the storage of the videos removed from the "new
     * videos" list. The methods loads the data from the HTML5 local storage.
     */
    function loadDeletedVideos() {
        var rawData, i, videoData, videoDetails;
        deletedVideos = {};
        rawData = localStorage.getItem('deletedVideos');
        if (!rawData) {
            return;
        }
        videoData = rawData.split('|');
        for (i = videoData.length; i--;) {
            videoDetails = videoData[i].split(':');
            deletedVideos[videoDetails[0]] = new Date(videoDetails[1] * 1000);
        }
    }

    /**
     * Serializes and saves to the HTML5 local storage the in-memory storage of
     * the videos removed from the "new videos" list by the user by either
     * watching them or removing them manually. Each element in the in-memory
     * storage is serialized as <code>"videoID:timestamp"</code> where
     * <code>videoID</code> is ID of the given video and <code>timestamp</code>
     * is a UNIX timestamp of the moment the video was removed from the "new
     * videos" list with the <strong>second-based precission</strong>.
     */
    function saveDeletedVideos() {
        var dumpedDetails, videoId, timestamp;
        dumpedDetails = [];
        for (videoId in deletedVideos) {
            if (!deletedVideos.hasOwnProperty(videoId)) {
                continue;
            }
            if (!deletedVideos[videoId]) {
                continue;
            }
            timestamp = Math.floor(deletedVideos[videoId].getTime() / 1000);
            dumpedDetails.push(videoId + ':' + timestamp);
        }
        localStorage.setItem('deletedVideos', dumpedDetails.join('|'));
    }

    /**
     * Imports the legacy filter data from the local storage. The legacy filter
     * data was used by the Filter class (might be non-existent at the moment).
     * The data contains a colon-separated list of IDs of videos that were
     * removed from the "new videos" list by the user. All these video IDs are
     * imported, stored in the new storage format and then the old storage is
     * deleted. The "video removed" timestamp is set to the current timestamp
     * for all the imported video records because the original time cannot be
     * determined.
     *
     * <p>Note that the method has no effect if the old storage does not exist
     * or is empty.</p>
     *
     * @deprecated This function is only used to import the old "deleted videos
     *             filter" data. It has not other use and will be deleted in a
     *             future release.
     */
    function importFilter() {
        var filter, videoIDs, i, deletedTimestamp;
        filter = localStorage.getItem('filter');
        if (!filter) {
            return;
        }
        videoIDs = filter.split(':');
        deletedTimestamp = new Date();
        for (i = videoIDs.length; i--;) {
            deletedVideos[videoIDs[i]] = deletedTimestamp;
        }
        saveDeletedVideos();
        localStorage.setItem('filter', '');
        localStorage.removeItem('filter');
    }

    return DeletedVideosMemory;
}());
